#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pthread.h>

#define worker_number 2
#define buf_size 4
typedef struct
{
    int is_end;
    char path[128];
    char string[128];
} task_t;

task_t *buffer[buf_size];
int in;
int out;

void find_file(char *path, char *target);

int buffer_empty()
{
    return in == out;
}
int buffer_full()
{
    return (in + 1) % buf_size == out;
}

task_t *get()
{
    task_t *item;
    item = buffer[out];
    out = (out + 1) % buf_size;
    return item;
}

void put(task_t *item)
{
    buffer[in] = item;
    in = (in + 1) % buf_size;
}

typedef struct
{
    int value;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} sema_t;

void sema_init(sema_t *sema, int value)
{
    sema->value = value;
    pthread_mutex_init(&sema->mutex, NULL);
    pthread_cond_init(&sema->cond, NULL);
}

void sema_wait(sema_t *sema)
{
    pthread_mutex_lock(&sema->mutex);
    while (sema->value <= 0)
        pthread_cond_wait(&sema->cond, &sema->mutex);
    sema->value--;
    pthread_mutex_unlock(&sema->mutex);
}

void sema_signal(sema_t *sema)
{
    pthread_mutex_lock(&sema->mutex);
    ++sema->value;
    pthread_cond_signal(&sema->cond);
    pthread_mutex_unlock(&sema->mutex);
}

sema_t mutex_sema;
sema_t empty_buffer;
sema_t full_buffer;

void *worker(void *arg)
{
    task_t *task;
    while (1)
    {
        sema_wait(&full_buffer);
        sema_wait(&mutex_sema);
        task = get();
        sema_signal(&mutex_sema);
        sema_signal(&empty_buffer);

        if (task->is_end)
            break;
        find_file(task->path, task->string);
    }
    return NULL;
}
void find_dir(char *path, char *target)
{
    DIR *dir = opendir(path);
    struct dirent *entry;
    // char b[40],c[20];
    // strcpy(b,"/");
    while (entry = readdir(dir))
    {
        if (strcmp(entry->d_name, ".") == 0)
            continue;
        if (strcmp(entry->d_name, "..") == 0)
            continue;

        if (entry->d_type == DT_DIR)
        {
            char temp[128];
            strcpy(temp, path);
            strcat(temp, "/");
            strcat(temp, entry->d_name);

            find_dir(temp, target);
        }
        if (entry->d_type == DT_REG)
        {
            char temp[128];
            strcpy(temp, path);
            strcat(temp, "/");
            strcat(temp, entry->d_name);

            task_t *task = (task_t *)malloc(sizeof(task_t));
            task->is_end = 0;
            strcpy(task->path, temp);
            strcpy(task->string, target);

            sema_wait(&empty_buffer);
            sema_wait(&mutex_sema);
            put(task);
            sema_signal(&mutex_sema);
            sema_signal(&full_buffer);
        }
    }
    closedir(dir);
}

void find_file(char *path, char *target)
{
    FILE *file = fopen(path, "r");
    char line[256];
    while (fgets(line, sizeof(line), file))
    {
        if (strstr(line, target))
            printf("%s %s", path, line);
    }
    fclose(file);
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        puts("Usage: pfind file string");
        return 0;
    }

    in = 0;
    out = 0;

    char *path = argv[1];
    char *string = argv[2];

    sema_init(&mutex_sema, 1);
    sema_init(&empty_buffer, buf_size);
    sema_init(&full_buffer, 0);

    pthread_t worker_tid[worker_number];
    for (int i = 0; i < worker_number; i++)
    {
        pthread_create(&worker_tid[i], NULL, worker, NULL);
    }

    struct stat info;
    stat(path, &info);
    if (S_ISDIR(info.st_mode))
        find_dir(path, string);
    else
    {
        find_file(path, string);
    }
    task_t *task = (task_t *)malloc(sizeof(task_t));
    task->is_end = 1;
    for (int i = 0; i < worker_number; i++)
    {
        sema_wait(&empty_buffer);
        sema_wait(&mutex_sema);
        put(task);
        sema_signal(&mutex_sema);
        sema_signal(&full_buffer);
    }
    for (int i = 0; i < worker_number; i++)
        pthread_join(worker_tid[i], NULL);
    return 0;
}